using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;

namespace ReactiveCode.Util.Expressions
{
    internal class ExpressionStack : ExpressionVisitor<Unit>
    {
        private readonly Stack<Expression> _data = new Stack<Expression>();

        public void PushIfNotNull(Expression exp)
        {
            if (exp != null)
                _data.Push(exp);
        }

        public void PushContent(Expression exp)
        {
            Visit(exp);
        }
        public void Push(Expression exp)
        {
            _data.Push(exp);
        }


        public Expression Pop()
        {
            return _data.Pop();
        }

        public int Count
        {
            get { return _data.Count; }
        }

        private void PushList(IEnumerable<Expression> expressions)
        {
            Contract.Requires(expressions != null); ;
            foreach(var expr in expressions.Reverse())
                PushIfNotNull(expr);
        }

        private void VisitInitializers(ReadOnlyCollection<ElementInit> initilizers)
        {
            for (int i = initilizers.Count - 1; i >= 0; i--)
                PushList(initilizers[i].Arguments);
        }

        protected override Unit VisitBinary(BinaryExpression exp)
        {
            PushIfNotNull(exp.Conversion);
            PushIfNotNull(exp.Right);
            PushIfNotNull(exp.Left);
            return base.VisitBinary(exp);
        }

        protected override Unit VisitConditional(ConditionalExpression exp)
        {
            PushIfNotNull(exp.IfFalse);
            PushIfNotNull(exp.IfTrue);
            PushIfNotNull(exp.Test);
            return base.VisitConditional(exp);
        }

        protected override Unit VisitInvocation(InvocationExpression exp)
        {
            PushList(exp.Arguments);
            PushIfNotNull(exp.Expression);
            return base.VisitInvocation(exp);
        }

        protected override Unit VisitLambda(LambdaExpression exp)
        {
            PushIfNotNull(exp.Body);
            for (int i = exp.Parameters.Count - 1; i >= 0; i--)
                PushIfNotNull(exp.Parameters[i]);
            return base.VisitLambda(exp);
        }

        protected override Unit VisitListInit(ListInitExpression exp)
        {
            VisitInitializers(exp.Initializers);
            PushIfNotNull(exp.NewExpression);
            return base.VisitListInit(exp);
        }

        protected override Unit VisitMember(MemberExpression exp)
        {
            PushIfNotNull(exp.Expression);
            return base.VisitMember(exp);
        }

        #region Member binding

        private void VisitBinding(MemberBinding mem)
        {
            switch (mem.BindingType)
            {
                case MemberBindingType.Assignment:
                    VisitAssignment((MemberAssignment)mem);
                    break;
                case MemberBindingType.ListBinding:
                    VisitListBinding((MemberListBinding)mem);
                    break;
                case MemberBindingType.MemberBinding:
                    VisitMemberBinding((MemberMemberBinding)mem);
                    break;
            }
        }

        private void VisitAssignment(MemberAssignment mem)
        {
            PushIfNotNull(mem.Expression);
        }

        private void VisitListBinding(MemberListBinding mem)
        {
            VisitInitializers(mem.Initializers);
        }

        private void VisitMemberBinding(MemberMemberBinding mem)
        {
            VisitBindings(mem.Bindings);
        }

        private void VisitBindings(ReadOnlyCollection<MemberBinding> bindings)
        {
            for (int i = bindings.Count - 1; i >= 0; i--)
                VisitBinding(bindings[i]);
        }

        #endregion

        protected override Unit VisitMemberInit(MemberInitExpression exp)
        {
            VisitBindings(exp.Bindings);
            PushIfNotNull(exp.NewExpression);
            return base.VisitMemberInit(exp);
        }

        protected override Unit VisitMethodCall(MethodCallExpression exp)
        {
            PushList(exp.Arguments);
            PushIfNotNull(exp.Object);
            return base.VisitMethodCall(exp);
        }

        protected override Unit VisitNewArray(NewArrayExpression exp)
        {
            PushList(exp.Expressions);
            return base.VisitNewArray(exp);
        }

        protected override Unit VisitNew(NewExpression exp)
        {
            PushList(exp.Arguments);
            return base.VisitNew(exp);
        }

        protected override Unit VisitUnary(UnaryExpression exp)
        {
            PushIfNotNull(exp.Operand);
            return base.VisitUnary(exp);
        }

        protected override Unit DefaultVisit(Expression exp)
        {
            return new Unit();
        }

        protected override Unit VisitBlock(BlockExpression exp)
        {
            PushList(exp.Expressions);
            PushList(exp.Variables);
            return base.VisitBlock(exp);
        }

        protected override Unit VisitIndex(IndexExpression exp)
        {
            PushList(exp.Arguments);
            PushIfNotNull(exp.Object);
            return base.VisitIndex(exp);
        }

        protected override Unit VisitLoop(LoopExpression exp)
        {
            PushIfNotNull(exp.Body);
            return base.VisitLoop(exp);
        }

        protected override Unit VisitGoto(GotoExpression exp)
        {
            PushIfNotNull(exp.Value);
            return base.VisitGoto(exp);
        }

        protected override Unit VisitDynamic(DynamicExpression exp)
        {
            PushList(exp.Arguments);
            return base.VisitDynamic(exp);
        }

        protected override Unit VisitRuntimeVariables(RuntimeVariablesExpression exp)
        {
            PushList(exp.Variables);
            return base.VisitRuntimeVariables(exp);
        }

        protected override Unit VisitSwitch(SwitchExpression exp)
        {
            foreach(var @case in exp.Cases.Reverse())
            {
                PushList(@case.TestValues);
                PushIfNotNull(@case.Body);
            }
            PushIfNotNull(exp.SwitchValue);
            return base.VisitSwitch(exp);
        }

        protected override Unit VisitTry(TryExpression exp)
        {
            PushIfNotNull(exp.Finally);
            PushIfNotNull(exp.Fault);
            foreach (var handler in exp.Handlers.Reverse())
            {
                PushIfNotNull(handler.Filter);
                PushIfNotNull(handler.Variable);
                PushIfNotNull(handler.Body);
            }
            PushIfNotNull(exp.Body);
            return base.VisitTry(exp);
        }

        protected override Unit VisitTypeBinary(TypeBinaryExpression exp)
        {
            PushIfNotNull(exp.Expression);
            return base.VisitTypeBinary(exp);
        }
    }
}
